/* Copyright (c) 2012 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 * $LastChangedRevision$
 */

#include "app_scheduler.h"
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "nrf_soc.h"
#include "nrf_assert.h"
#include "ble_sdk_util.h"


/**@brief Structure for holding a scheduled event. */
typedef struct 
{
    app_sched_event_handler_t handler;                      /**< Pointer to event handler to receive the event. */
    app_sched_event_data_t    data;                         /**< Data to be passed to the event handler. */
} app_sched_event_t;

/**@brief Event queue structure.
 *
 * @details The event queue is a circular buffer with both start and end pointers. 
 *          The used memory space is one more than the available queue size,
 *          so it's possible to distinguish between full and empty queue.
 */
typedef struct
{
    app_sched_event_t queue_data[APP_SCHED_QUEUE_SIZE + 1]; /**< Array for holding the queue data. */
    uint8_t           start;                                /**< Index of queue entry at the start of the queue. */
    uint8_t           end;                                  /**< Index of queue entry at the end of the queue. */
} app_sched_queue_t;

static volatile app_sched_queue_t s_app_sched_queue[APP_SCHED_QUEUE_NUM];

/**@brief Macro for incrementing a queue index. */
#define NEXT_INDEX(INDEX)\
    (((INDEX) + 1) % (APP_SCHED_QUEUE_SIZE + 1))

/**@brief Macro for checking if a queue is full. */
#define APP_SCHED_QUEUE_FULL(queue_id)\
    (NEXT_INDEX(s_app_sched_queue[queue_id].end) == s_app_sched_queue[queue_id].start)

/**@brief Macro for checking if a queue is empty. */
#define APP_SCHED_QUEUE_EMPTY(queue_id)\
    (s_app_sched_queue[queue_id].end == s_app_sched_queue[queue_id].start)


void app_sched_init(void)
{
    uint32_t i;
    
    for (i = 0; i < APP_SCHED_QUEUE_NUM; i++)
    {
        s_app_sched_queue[i].end   = 0;
        s_app_sched_queue[i].start = 0;
    }
}


/**@brief Reads the next event from specified event queue.
 *
 * @param[out]  p_event   Pointer to structure where the event will be put.
 * @param[in]   queue_id  ID of the queue to read.
 * @return      NRF_SUCCESS if new event, NRF_ERROR_NOT_FOUND if event queue is empty.
 */
static uint32_t app_sched_event_get(app_sched_event_t * p_event, uint8_t queue_id)
{
    uint32_t ret_status = NRF_ERROR_NOT_FOUND;

    if(!APP_SCHED_QUEUE_EMPTY(queue_id))
    { 
        volatile app_sched_queue_t * p_queue = &s_app_sched_queue[queue_id];
        
        CRITICAL_REGION_ENTER();

        *p_event = p_queue->queue_data[p_queue->start];
        p_queue->start = NEXT_INDEX(p_queue->start);

        CRITICAL_REGION_EXIT();

        ret_status = NRF_SUCCESS;
    }

    return ret_status;
}


void app_sched_schedule(void)
{
    static app_sched_event_t this_event;
    uint8_t                  this_queue_id = 0;

    // Check queues (lowest id is highest priority)
    while (this_queue_id < APP_SCHED_QUEUE_NUM)
    {
        // Get next event from this_queue (if any), and execute handler
        while ((app_sched_event_get(&this_event, this_queue_id) == NRF_SUCCESS))
        {
            this_event.handler(&this_event.data);
            this_queue_id = 0;
        }

        // No more events in current queue, check next queue
        this_queue_id++;
    }
}


uint32_t app_sched_event_put(app_sched_event_data_t *  p_data,
                             app_sched_event_handler_t handler,
                             uint8_t                   queue_id)
{
    uint32_t ret_status = NRF_ERROR_NO_MEM;

    if (queue_id < APP_SCHED_QUEUE_NUM)
    {
        if (!APP_SCHED_QUEUE_FULL(queue_id))
        {
            volatile app_sched_queue_t * p_queue = &s_app_sched_queue[queue_id];
            
            CRITICAL_REGION_ENTER();
            
            p_queue->queue_data[p_queue->end].handler = handler;
            p_queue->queue_data[p_queue->end].data    = *p_data;
            p_queue->end                              = NEXT_INDEX(p_queue->end);
            
            CRITICAL_REGION_EXIT();

            ret_status = NRF_SUCCESS;
        }
    }
    else
    {
        ret_status = NRF_ERROR_INVALID_PARAM;
    }

    return ret_status;
}


static void app_sched_timeout_evt_handler(app_sched_event_data_t * p_ev_data)
{
    (*p_ev_data->data.timer.timeout_handler)(p_ev_data->data.timer.timer_id);
}


uint32_t app_sched_timer_event_schedule(app_timer_id_t              timer_id,
                                        app_timer_timeout_handler_t timeout_handler,
                                        uint8_t                     priority)
{
    app_sched_event_data_t ev_dat;
    
    if (priority >= APP_SCHED_QUEUE_NUM)
    {
        priority = APP_SCHED_QUEUE_NUM - 1;
    }

    ev_dat.event_id                   = APP_SCHED_EVENT_TIMER;
    ev_dat.data.timer.timer_id        = timer_id;
    ev_dat.data.timer.timeout_handler = timeout_handler;
    
    return app_sched_event_put(&ev_dat, app_sched_timeout_evt_handler, priority);
}
